home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 3: Developer Tools / Linux Cubed Series 3 - Developer Tools.iso / devel / lang / lisp / stk-3.0 / stk-3 / blt-for-STk-3.0 / blt-1.9 / src / bltBgexec.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-12-19  |  26.4 KB  |  962 lines

  1.  
  2. /*
  3.  * bltBgexec.c --
  4.  *
  5.  *    This module implements a background "exec" command for the
  6.  *    Tk toolkit.
  7.  *
  8.  * Copyright 1993-1994 by AT&T Bell Laboratories.
  9.  * Permission to use, copy, modify, and distribute this software
  10.  * and its documentation for any purpose and without fee is hereby
  11.  * granted, provided that the above copyright notice appear in all
  12.  * copies and that both that the copyright notice and warranty
  13.  * disclaimer appear in supporting documentation, and that the
  14.  * names of AT&T Bell Laboratories any of their entities not be used
  15.  * in advertising or publicity pertaining to distribution of the
  16.  * software without specific, written prior permission.
  17.  *
  18.  * AT&T disclaims all warranties with regard to this software, including
  19.  * all implied warranties of merchantability and fitness.  In no event
  20.  * shall AT&T be liable for any special, indirect or consequential
  21.  * damages or any damages whatsoever resulting from loss of use, data
  22.  * or profits, whether in an action of contract, negligence or other
  23.  * tortuous action, arising out of or in connection with the use or
  24.  * performance of this software.
  25.  *
  26.  * bgexec command created by George Howlett.
  27.  */
  28.  
  29. #include "blt.h"
  30.  
  31. #include <fcntl.h>
  32. #include <signal.h>
  33. #include <sys/param.h>
  34. #include <sys/types.h>
  35. #ifdef HAVE_SYS_WAIT_H
  36. #   include <sys/wait.h>
  37. #endif
  38.  
  39. /* The wait-related definitions are taken from tclUnix.h */
  40.  
  41. /*
  42.  * Not all systems declare the errno variable in errno.h. so this
  43.  * file does it explicitly.  The list of system error messages also
  44.  * isn't generally declared in a header file anywhere.
  45.  */
  46.  
  47. extern int errno;
  48.  
  49. /*
  50.  * The type of the status returned by wait varies from UNIX system
  51.  * to UNIX system.  The macro below defines it:
  52.  */
  53.  
  54. #ifdef AIX
  55. #   define WAIT_STATUS_TYPE pid_t
  56. #else
  57. #ifndef NO_UNION_WAIT
  58. #   define WAIT_STATUS_TYPE union wait
  59. #else
  60. #   define WAIT_STATUS_TYPE int
  61. #endif
  62. #endif
  63.  
  64. /*
  65.  * Supply definitions for macros to query wait status, if not already
  66.  * defined in header files above.
  67.  */
  68.  
  69. #ifndef WIFEXITED
  70. #   define WIFEXITED(stat)  (((*((int *) &(stat))) & 0xff) == 0)
  71. #endif
  72.  
  73. #ifndef WEXITSTATUS
  74. #   define WEXITSTATUS(stat) (((*((int *) &(stat))) >> 8) & 0xff)
  75. #endif
  76.  
  77. #ifndef WIFSIGNALED
  78. #   define WIFSIGNALED(stat) (((*((int *) &(stat)))) && ((*((int *) &(stat))) == ((*((int *) &(stat))) & 0x00ff)))
  79. #endif
  80.  
  81. #ifndef WTERMSIG
  82. #   define WTERMSIG(stat)    ((*((int *) &(stat))) & 0x7f)
  83. #endif
  84.  
  85. #ifndef WIFSTOPPED
  86. #   define WIFSTOPPED(stat)  (((*((int *) &(stat))) & 0xff) == 0177)
  87. #endif
  88.  
  89. #ifndef WSTOPSIG
  90. #   define WSTOPSIG(stat)    (((*((int *) &(stat))) >> 8) & 0xff)
  91. #endif
  92.  
  93. #ifndef BGEXEC_VERSION
  94. #define BGEXEC_VERSION "1.4"
  95. #endif
  96.  
  97. #define BUFFER_SIZE    1000    /* Maximum number of bytes per read */
  98. #define MAX_READS       100    /* Maximum number of successful reads
  99.                      * before stopping to let Tk catch up
  100.                      * on events */
  101.  
  102. typedef struct {
  103.     char *storage;        /* Buffer to store command output
  104.                  * (malloc-ed): Initially points to
  105.                  * static storage */
  106.     unsigned int used;        /* Number of characters read into the
  107.                  * buffer */
  108.     unsigned int size;        /* Size of buffer allocated */
  109.     char staticSpace[BUFFER_SIZE * 2 + 1];    /* Static buffer space */
  110.  
  111. } Buffer;
  112.  
  113. typedef struct {
  114.     Tcl_Interp *interp;        /* Interpreter containing variable */
  115.  
  116.     char *updateName;        /* Name of a Tcl variable (malloc'ed)
  117.                  * to be updated when no more data is
  118.                  * currently available for reading
  119.                  * from the output pipe.  It's
  120.                  * appended with the contents of the
  121.                  * current buffer (data which has
  122.                  * arrived since the last idle
  123.                  * point). If it's NULL, no updates
  124.                  * are made */
  125.  
  126.     char *outputName;        /* Name of a Tcl variable (malloc'ed)
  127.                  * to be set with the contents of
  128.                  * stdout after the last UNIX
  129.                  * subprocess has completed. Setting
  130.                  * this variable triggers the
  131.                  * termination of all subprocesses,
  132.                  * regardless whether they have
  133.                  * already completed or not */
  134.  
  135.     char *errorName;        /* Name of a Tcl variable (malloc'ed)
  136.                  * to hold any available data from
  137.                  * standard error */
  138.  
  139.     char *statusName;        /* Name of a Tcl variable (malloc'ed)
  140.                  * to hold exit status of the last
  141.                  * process.
  142.                  */
  143.  
  144.     Tk_TimerToken timerToken;    /* Token for timer handler which polls
  145.                  * for the exit status of each
  146.                  * sub-process. If zero, there's no
  147.                  * timer handler queued. */
  148.  
  149.     int outputId;        /* File descriptor for output pipe.  */
  150.     int errorId;        /* File Descriptor for error file. */
  151.  
  152.     Buffer buffer;        /* Buffer storing subprocess' stdin/stderr */
  153.  
  154.     unsigned int numPids;    /* Number of processes created in pipeline */
  155.     int *pidPtr;        /* Array of process Ids. */
  156.  
  157.     unsigned int sigNum;    /* If non-zero, indicates signal to send
  158.                  * subprocesses when cleaning up.*/
  159.     int keepFlag;        /* Indicates to set Tcl output
  160.                  * variables with trailing newlines
  161.                  * intact */
  162.     unsigned int lastCount;    /* Number of bytes read the last time a
  163.                  * buffer was retrieved */
  164.     int fixMark;        /* Index of fixed newline character in
  165.                  * buffer.  If -1, no fix was made. */
  166.  
  167. } BackgroundInfo;
  168.  
  169. /*
  170.  *----------------------------------------------------------------------
  171.  *
  172.  * GetBuffer --
  173.  *
  174.  *    Returns the output currently saved in buffer storage
  175.  *
  176.  *----------------------------------------------------------------------
  177.  */
  178. static char *
  179. GetBuffer(bufferPtr)
  180.     Buffer *bufferPtr;
  181. {
  182.     bufferPtr->storage[bufferPtr->used] = '\0';
  183.     return (bufferPtr->storage);
  184. }
  185.  
  186. /*
  187.  *----------------------------------------------------------------------
  188.  *
  189.  * InitBuffer --
  190.  *
  191.  *    Initializes the buffer storage, clearing any output that may
  192.  *    have accumulated from previous usage.
  193.  *
  194.  * Results:
  195.  *    None.
  196.  *
  197.  * Side effects:
  198.  *    Buffer storage is cleared.
  199.  *
  200.  *---------------------------------------------------------------------- */
  201. static void
  202. InitBuffer(bufferPtr)
  203.     Buffer *bufferPtr;
  204. {
  205.     bufferPtr->storage = bufferPtr->staticSpace;
  206.     bufferPtr->size = BUFFER_SIZE * 2;
  207.     bufferPtr->storage[0] = '\0';
  208.     bufferPtr->used = 0;
  209. }
  210.  
  211. /*
  212.  *----------------------------------------------------------------------
  213.  *
  214.  * ResetBuffer --
  215.  *
  216.  *    Resets the buffer storage, freeing any malloc'ed space.
  217.  *
  218.  * Results:
  219.  *    None.
  220.  *
  221.  *----------------------------------------------------------------------
  222.  */
  223. static void
  224. ResetBuffer(bufferPtr)
  225.     Buffer *bufferPtr;
  226. {
  227.     if (bufferPtr->storage != bufferPtr->staticSpace) {
  228.     free(bufferPtr->storage);
  229.     }
  230.     InitBuffer(bufferPtr);
  231. }
  232.  
  233. /*
  234.  *----------------------------------------------------------------------
  235.  *
  236.  * GrowBuffer --
  237.  *
  238.  *    Doubles the size of the current buffer.
  239.  *
  240.  * Results:
  241.  *    None.
  242.  *
  243.  *----------------------------------------------------------------------
  244.  */
  245. static int
  246. GrowBuffer(bufferPtr)
  247.     Buffer *bufferPtr;
  248. {
  249.     char *newPtr;
  250.  
  251.     /*
  252.      * Allocate a new buffer, double the old size
  253.      */
  254.  
  255.     bufferPtr->size += bufferPtr->size;
  256.     newPtr = (char *)malloc(sizeof(char) * (bufferPtr->size + 1));
  257.     if (newPtr == NULL) {
  258.     return TCL_ERROR;
  259.     }
  260.     strcpy(newPtr, bufferPtr->storage);
  261.     if (bufferPtr->storage != bufferPtr->staticSpace) {
  262.     free((char *)bufferPtr->storage);
  263.     }
  264.     bufferPtr->storage = newPtr;
  265.     return TCL_OK;
  266. }
  267.  
  268. /*
  269.  *----------------------------------------------------------------------
  270.  *
  271.  * AppendOutputToBuffer --
  272.  *
  273.  *    Appends any available data from a given file descriptor to the
  274.  *    buffer.
  275.  *
  276.  * Results:
  277.  *    Returns TCL_OK when EOF is found, TCL_RETURN if reading
  278.  *    data would block, and TCL_ERROR if an error occured.
  279.  *
  280.  *----------------------------------------------------------------------
  281.  */
  282. static int
  283. AppendOutputToBuffer(f, bufferPtr)
  284.     int f;
  285.     Buffer *bufferPtr;
  286. {
  287.     int numBytes, bytesLeft;
  288.     register int i, n;
  289.     char *array;
  290.  
  291.     /*
  292.      * ------------------------------------------------------------------
  293.      *
  294.      * Worry about indefinite postponement.
  295.      *
  296.      * Typically we want to stay in the read loop as long as it takes
  297.      * to collect all the data that's currently available.  But if
  298.      * it's coming in at a constant high rate, we need to arbitrarily
  299.      * break out at some point. This allows for both setting the
  300.      * output variable and the Tk program to handle idle events.
  301.      *
  302.      * ------------------------------------------------------------------
  303.      */
  304.  
  305.     for (i = 0; i < MAX_READS; i++) {
  306.  
  307.     /*
  308.      * Allocate a larger buffer when the number of remaining bytes
  309.      * is below a threshold (BUFFER_SIZE).
  310.      */
  311.  
  312.     bytesLeft = bufferPtr->size - bufferPtr->used;
  313.     if (bytesLeft < BUFFER_SIZE) {
  314.         GrowBuffer(bufferPtr);
  315.         bytesLeft = bufferPtr->size - bufferPtr->used;
  316.     }
  317.         array = bufferPtr->storage + bufferPtr->used;
  318.     numBytes = read(f, array, bytesLeft);
  319.  
  320.     if (numBytes == 0) {    /* EOF: break out of loop. */
  321.         return TCL_OK;
  322.     }
  323.     if (numBytes < 0) {
  324.  
  325.         /*
  326.          * Either an error has occurred or no more data is
  327.          * currently available to read.
  328.          */
  329. #ifdef O_NONBLOCK
  330.         if (errno == EAGAIN) {
  331. #else
  332.         if (errno == EWOULDBLOCK) {
  333. #endif /*O_NONBLOCK*/
  334.         break;
  335.         }
  336.         bufferPtr->storage[0] = '\0';
  337.         return TCL_ERROR;
  338.     }
  339.         /* Clean out NUL bytes, make spaces */
  340.         for (n = 0; n < numBytes; n++) {
  341.         if (array[n] == 0) {
  342.         array[n] = ' ';
  343.         }
  344.     }
  345.     bufferPtr->used += numBytes;
  346.     bufferPtr->storage[bufferPtr->used] = '\0';
  347.     }
  348.     return TCL_RETURN;
  349. }
  350.  
  351. /*
  352.  *----------------------------------------------------------------------
  353.  *
  354.  * FixNewline --
  355.  *
  356.  *    Clips off the trailing newline in the buffer (if one exists).
  357.  *    Saves the location in the buffer where the fix was made.
  358.  *
  359.  *---------------------------------------------------------------------- */
  360. static void
  361. FixNewline(infoPtr)
  362.     BackgroundInfo *infoPtr;
  363. {
  364.     Buffer *bufferPtr = &(infoPtr->buffer);
  365.  
  366.     infoPtr->fixMark = -1;
  367.     if (bufferPtr->used > 0) {
  368.     int mark = bufferPtr->used - 1;
  369.  
  370.     if (bufferPtr->storage[mark] == '\n') {
  371.         bufferPtr->storage[mark] = '\0';
  372.         infoPtr->fixMark = mark;
  373.     }
  374.     }
  375. }
  376.  
  377. /*
  378.  *----------------------------------------------------------------------
  379.  *
  380.  * UnfixNewline --
  381.  *
  382.  *    Restores the previously clipped newline in the buffer.
  383.  *    The fixMark field indicates whether one was clipped.
  384.  *
  385.  *----------------------------------------------------------------------
  386.  */
  387. static void
  388. UnfixNewline(infoPtr)
  389.     BackgroundInfo *infoPtr;
  390. {
  391.     Buffer *bufferPtr = &(infoPtr->buffer);
  392.  
  393.     if (infoPtr->fixMark >= 0) {
  394.     bufferPtr->storage[infoPtr->fixMark] = '\n';
  395.     infoPtr->fixMark = -1;
  396.     }
  397. }
  398.  
  399. /*
  400.  *----------------------------------------------------------------------
  401.  *
  402.  * GetLastAppended --
  403.  *
  404.  *    Returns the output saved from the last time this routine
  405.  *    was called.
  406.  *
  407.  *----------------------------------------------------------------------
  408.  */
  409. static char *
  410. GetLastAppended(infoPtr)
  411.     BackgroundInfo *infoPtr;
  412. {
  413.     Buffer *bufferPtr = &(infoPtr->buffer);
  414.     char *string;
  415.  
  416.     bufferPtr->storage[bufferPtr->used] = '\0';
  417.     string = bufferPtr->storage + infoPtr->lastCount;
  418.     infoPtr->lastCount = bufferPtr->used;
  419.     return (string);
  420. }
  421.  
  422. /*
  423.  *----------------------------------------------------------------------
  424.  *
  425.  * DestroyBackgroundInfo --
  426.  *
  427.  *     This procedure is invoked by Tk_EventuallyFree or Tk_Release
  428.  *     to clean up the internal structure (BackgroundInfo) at a safe
  429.  *     time (when no-one is using it anymore).
  430.  *
  431.  *    Right now, our only concern is protecting infoPtr->outputName,
  432.  *    since subsequent calls to trace procedures (via CallTraces)
  433.  *    may still use it (as part1 and possibly part2).
  434.  *
  435.  * Results:
  436.  *    None.
  437.  *
  438.  * Side effects:
  439.  *    The memory allocated to the BackgroundInfo structure released.
  440.  *
  441.  *---------------------------------------------------------------------- */
  442.  
  443.  /* ARGSUSED */
  444. static void
  445. DestroyBackgroundInfo(clientData)
  446.     ClientData clientData;    /* Background info record. */
  447. {
  448.     BackgroundInfo *infoPtr = (BackgroundInfo *)clientData;
  449.  
  450.     ResetBuffer(&(infoPtr->buffer));
  451.     if (infoPtr->updateName != NULL) {
  452.     free(infoPtr->updateName);
  453.     }
  454.     if (infoPtr->outputName != NULL) {
  455.     free(infoPtr->outputName);
  456.     }
  457.     if (infoPtr->errorName != NULL) {
  458.     free(infoPtr->errorName);
  459.     }
  460.     if (infoPtr->statusName != NULL) {
  461.     free(infoPtr->statusName);
  462.     }
  463.     if (infoPtr->pidPtr != NULL) {
  464.     if (infoPtr->numPids > 0) {
  465.         Tcl_DetachPids(infoPtr->numPids, infoPtr->pidPtr);
  466.     }
  467.     Tcl_ReapDetachedProcs();
  468.     free((char *)infoPtr->pidPtr);
  469.     }
  470.     free((char *)infoPtr);
  471. }
  472.  
  473. /*
  474.  * ----------------------------------------------------------------------
  475.  *
  476.  * CleanupProc --
  477.  *
  478.  *    This procedure cleans up the BackgroundInfo data structure
  479.  *    associated with the detached subprocesses.  It is called when
  480.  *    the variable associated with UNIX subprocesses has been
  481.  *    overwritten.  This usually occurs when the subprocesses have
  482.  *    completed or an error was detected.  However, it may be used
  483.  *    to terminate the detached processes from the Tcl program by
  484.  *    setting the associated variable.
  485.  *
  486.  * Results:
  487.  *    Always returns NULL.  Only called from a variable trace.
  488.  *
  489.  * Side effects:
  490.  *    The output descriptor is closed and the variable trace is
  491.  *    deleted.  In addition, the subprocesses are signaled for
  492.  *    termination.
  493.  *
  494.  * ---------------------------------------------------------------------- 
  495.  */
  496. static char *
  497. CleanupProc(clientData, interp, part1, part2, flags)
  498.     ClientData clientData;    /* File output information. */
  499.     Tcl_Interp *interp;
  500.     char *part1, *part2;
  501.     int flags;
  502. {
  503.     BackgroundInfo *infoPtr = (BackgroundInfo *)clientData;
  504.  
  505.     if (!(flags & (TCL_TRACE_WRITES | TCL_GLOBAL_ONLY))) {
  506.     return NULL;
  507.     }
  508.     if (infoPtr->outputId != -1) {
  509.     close(infoPtr->outputId);
  510.     Tk_DeleteFileHandler(infoPtr->outputId);
  511.     }
  512.     if (infoPtr->timerToken != (Tk_TimerToken) 0) {
  513.     Tk_DeleteTimerHandler(infoPtr->timerToken);
  514.     }
  515.     if (infoPtr->errorId >= 0) {
  516.  
  517.     /*
  518.      * If an error variable needs to be set, reset the error file
  519.      * descriptor and read the captured stderr from the temporary
  520.      * file
  521.      */
  522.  
  523.     if ((infoPtr->errorName != NULL) &&
  524.         (lseek(infoPtr->errorId, 0L, 0) >= 0)) {
  525.         int result;
  526.  
  527.         ResetBuffer(&(infoPtr->buffer));
  528.         do {
  529.         result = AppendOutputToBuffer(infoPtr->errorId,
  530.             &(infoPtr->buffer));
  531.         } while (result == TCL_RETURN);
  532.  
  533.         if (result == TCL_OK) {
  534.         if (!infoPtr->keepFlag) {
  535.             FixNewline(infoPtr);
  536.         }
  537.         Tcl_SetVar(infoPtr->interp, infoPtr->errorName,
  538.             GetBuffer(&(infoPtr->buffer)), TCL_GLOBAL_ONLY);
  539.         } else if (result == TCL_ERROR) {
  540.         Tcl_AppendResult(infoPtr->interp, "error appending buffer: ",
  541.             Tcl_PosixError(infoPtr->interp), (char *)NULL);
  542.         Tk_BackgroundError(infoPtr->interp);
  543.         }
  544.     }
  545.     close(infoPtr->errorId);
  546.     }
  547.     Tcl_UntraceVar2(interp, part1, part2, flags, CleanupProc, clientData);
  548.  
  549.     if ((infoPtr->pidPtr != NULL) && (infoPtr->sigNum > 0)) {
  550.     register int i;
  551.  
  552.     for (i = 0; i < infoPtr->numPids; i++) {
  553.         kill(infoPtr->pidPtr[i], (int)infoPtr->sigNum);
  554.     }
  555.     }
  556. #if (TK_MINOR_VERSION > 0)
  557.     Tk_EventuallyFree((ClientData)infoPtr, (Tcl_FreeProc *)DestroyBackgroundInfo);
  558. #else
  559.     Tk_EventuallyFree((ClientData)infoPtr, (Tk_FreeProc *)DestroyBackgroundInfo);
  560. #endif
  561.     return NULL;
  562. }
  563.  
  564. /*
  565.  *----------------------------------------------------------------------
  566.  *
  567.  * StatusProc --
  568.  *
  569.  *    This is a timer handler procedure which gets called
  570.  *    periodically to reap any of the sub-processes if they have
  571.  *    terminated.  After the last process has terminated, the
  572.  *    contents of standard output (saved in infoPtr->buffer) are
  573.  *    stored in the output variable, which triggers the cleanup
  574.  *    proc (using a variable trace). If the status variable is
  575.  *    active (infoPtr->statusName != NULL), then set the status the
  576.  *    last process to exit in the status variable.
  577.  *
  578.  * Results:
  579.  *    None.  Called from the Tk event loop.
  580.  *
  581.  * Side effects:
  582.  *    Many. The contents of pidPtr is shifted, leaving only those
  583.  *    sub-processes which have not yet terminated.  If there are
  584.  *    still subprocesses left, this procedure is placed in the timer
  585.  *    queue again. Otherwise the output and possibly the status
  586.  *    variables are updated.  The former triggers the cleanup
  587.  *    routine which will destroy the information and resources
  588.  *    associated with these background processes.
  589.  *
  590.  *---------------------------------------------------------------------- 
  591.  */
  592. static void
  593. StatusProc(clientData)
  594.     ClientData clientData;
  595. {
  596.     BackgroundInfo *infoPtr = (BackgroundInfo *)clientData;
  597.     register int i;
  598.     int result;
  599.     WAIT_STATUS_TYPE waitStatus;
  600.     char *statusMesg, *statusInfo;
  601.     int numLeft;        /* Number of processes still not reaped */
  602.  
  603. #ifdef notdef
  604.     fprintf(stderr, "in StatusProc(numPids=%d)\n", infoPtr->numPids);
  605. #endif
  606.     numLeft = 0;
  607.     for (i = 0; i < infoPtr->numPids; i++) {
  608.     result = waitpid(infoPtr->pidPtr[i], (int *)&waitStatus, WNOHANG);
  609.     if ((result == 0) || ((result == -1) && (errno != ECHILD))) {
  610.         if (numLeft < i) {
  611.         infoPtr->pidPtr[numLeft] = infoPtr->pidPtr[i];
  612.         }
  613.         numLeft++;
  614.         continue;
  615.     }
  616.     /*
  617.      * Collect the status information associated with the subprocess.
  618.      * We'll use it only if this is the last subprocess to be reaped.
  619.      */
  620.     if (WIFEXITED(waitStatus)) {
  621.         statusInfo = "Child completed";
  622.         statusMesg = "CHILDSTATUS";
  623.     } else if (WIFSIGNALED(waitStatus)) {
  624.         statusInfo = Tcl_SignalMsg((int)(WTERMSIG(waitStatus)));
  625.         statusMesg = "CHILDKILLED";
  626.     } else if (WIFSTOPPED(waitStatus)) {
  627.         statusInfo = Tcl_SignalMsg((int)(WSTOPSIG(waitStatus)));
  628.         statusMesg = "CHILDSUSP";
  629.     } else {
  630.         statusMesg = "UNKNOWN";
  631.         statusInfo = "Child status unknown";
  632.     }
  633.     }
  634.  
  635.     infoPtr->numPids = numLeft;
  636.     if (numLeft > 0) {
  637.     /* Keep polling for the status of the children that are left */
  638.     infoPtr->timerToken = Tk_CreateTimerHandler(1000, StatusProc,
  639.         (ClientData)infoPtr);
  640.     } else {
  641.     if (infoPtr->statusName != NULL) {
  642.         Tcl_DString buffer;
  643.         char string[20];
  644.  
  645.         /*
  646.          * Set the status variable with the status of the last
  647.          * process reaped.  The status is a list of an error token,
  648.          * the exit status, and a message.
  649.          */
  650.  
  651.         Tcl_DStringInit(&buffer);
  652.         Tcl_DStringAppendElement(&buffer, statusMesg);
  653.         sprintf(string, "%d", WEXITSTATUS(waitStatus));
  654.         Tcl_DStringAppendElement(&buffer, string);
  655.         Tcl_DStringAppendElement(&buffer, statusInfo);
  656.  
  657.         Tcl_SetVar(infoPtr->interp, infoPtr->statusName,
  658.         Tcl_DStringValue(&buffer), TCL_GLOBAL_ONLY);
  659.         Tcl_DStringFree(&buffer);
  660.     }
  661.     /*
  662.      * Setting the output variable also triggers the cleanup
  663.      * procedure which frees memory and destroys the variable
  664.      * traces.
  665.      */
  666.     Tk_Preserve((ClientData)infoPtr);
  667.     Tcl_SetVar(infoPtr->interp, infoPtr->outputName,
  668.         GetBuffer(&(infoPtr->buffer)), TCL_GLOBAL_ONLY);
  669.     Tk_Release((ClientData)infoPtr);
  670.     }
  671. }
  672.  
  673. /*
  674.  *----------------------------------------------------------------------
  675.  *
  676.  * BackgroundProc --
  677.  *
  678.  *    This procedure is called when output from the detached command
  679.  *    is available.  The output is read and saved in a buffer in the
  680.  *    BackgroundInfo structure.
  681.  *
  682.  * Results:
  683.  *    None.
  684.  *
  685.  * Side effects:
  686.  *    Data is stored in infoPtr->buffer.  This character array may
  687.  *    be increased as more space is required to contain the output
  688.  *    of the command.
  689.  *
  690.  *---------------------------------------------------------------------- */
  691.  /* ARGSUSED */
  692. static void
  693. BackgroundProc(clientData, mask)
  694.     ClientData clientData;    /* File output information. */
  695.     int mask;            /* Not used. */
  696. {
  697.     BackgroundInfo *infoPtr = (BackgroundInfo *)clientData;
  698.     int result;
  699.  
  700.     result = AppendOutputToBuffer(infoPtr->outputId, &(infoPtr->buffer));
  701.     if (result == TCL_RETURN) {
  702.     if (infoPtr->updateName != NULL) {
  703.         if (!infoPtr->keepFlag) {
  704.         FixNewline(infoPtr);
  705.         }
  706.         Tcl_SetVar(infoPtr->interp, infoPtr->updateName,
  707.         GetLastAppended(infoPtr),(TCL_GLOBAL_ONLY | TCL_APPEND_VALUE));
  708.         if (!infoPtr->keepFlag) {
  709.         UnfixNewline(infoPtr);
  710.         }
  711.     }
  712.     return;
  713.     }
  714.     if (result == TCL_ERROR) {
  715.     Tcl_AppendResult(infoPtr->interp, "error appending buffer: ",
  716.         Tcl_PosixError(infoPtr->interp), (char *)NULL);
  717.     Tk_BackgroundError(infoPtr->interp);
  718.     }
  719.     if (!infoPtr->keepFlag) {
  720.     FixNewline(infoPtr);
  721.     }
  722.     if (infoPtr->updateName != NULL) {
  723.     Tcl_SetVar(infoPtr->interp, infoPtr->updateName,
  724.         GetLastAppended(infoPtr), (TCL_GLOBAL_ONLY | TCL_APPEND_VALUE));
  725.     }
  726.     /*
  727.      * We're here if we've seen EOF or an error has occurred.  In
  728.      * either case, set up a timer handler to periodically poll for
  729.      * exit status of each process.  Initially check at the next idle
  730.      * interval.
  731.      */
  732.  
  733.     infoPtr->timerToken = Tk_CreateTimerHandler(0, StatusProc,
  734.     (ClientData)infoPtr);
  735.  
  736.     /* Delete the file handler and close the file descriptor. */
  737.  
  738.     close(infoPtr->outputId);
  739.     Tk_DeleteFileHandler(infoPtr->outputId);
  740.     infoPtr->outputId = -1;
  741. }
  742.  
  743. /*
  744.  *----------------------------------------------------------------------
  745.  *
  746.  * Blt_BgExecCmd --
  747.  *
  748.  *    This procedure is invoked to process the "bgexec" Tcl command.
  749.  *    See the user documentation for details on what it does.
  750.  *
  751.  * Results:
  752.  *    A standard Tcl result.
  753.  *
  754.  * Side effects:
  755.  *    See the user documentation.
  756.  *
  757.  *---------------------------------------------------------------------- 
  758.  */
  759.  /* ARGSUSED */
  760. static int
  761. BgExecCmd(clientData, interp, argc, argv)
  762.     ClientData clientData;    /* Not used. */
  763.     Tcl_Interp *interp;        /* Current interpreter. */
  764.     int argc;            /* Number of arguments. */
  765.     char **argv;        /* Argument strings. */
  766. {
  767.     int outputId;        /* File id for output pipe.  -1
  768.                  * means command overrode. */
  769.     int errorId = -1;
  770.     int *errFilePtr;
  771.     int *pidPtr;
  772.     int numPids;
  773.     BackgroundInfo *infoPtr;
  774.     register int i;
  775.     int parseSwitches;
  776.  
  777.     if (argc < 3) {
  778.     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  779.         " ?switches? varName command args", (char *)NULL);
  780.     return TCL_ERROR;
  781.     }
  782.     infoPtr = (BackgroundInfo *)malloc(sizeof(BackgroundInfo));
  783.     if (infoPtr == NULL) {
  784.     interp->result = "can't allocate file info structure";
  785.     return TCL_ERROR;
  786.     }
  787.     /* Initialize the background information structure */
  788.  
  789.     infoPtr->interp = interp;
  790.     infoPtr->timerToken = (Tk_TimerToken) 0;
  791.     infoPtr->keepFlag = 0;
  792.     infoPtr->sigNum = SIGHUP;
  793.     infoPtr->errorName = NULL;
  794.     infoPtr->updateName = NULL;
  795.     infoPtr->statusName = NULL;
  796.     infoPtr->outputName = NULL;
  797.     InitBuffer(&(infoPtr->buffer));
  798.  
  799.     parseSwitches = 1;
  800.     errFilePtr = NULL;        /* By default, stderr goes to the tty */
  801.  
  802.     for (i = 1; i < argc; i++) {
  803.     if ((parseSwitches) && (argv[i][0] == '-')) {
  804.         register char *swtch;
  805.         int length;
  806.         char c;
  807.  
  808.         swtch = argv[i];
  809.         length = strlen(swtch);
  810.         c = swtch[1];
  811.  
  812.         if ((c == '-') && (swtch[2] == '\0')) {
  813.         parseSwitches = 0;
  814.         continue;
  815.         } else if ((c == 'k') && (length > 1) &&
  816.         (strncmp(swtch, "-keepnewline", length) == 0)) {
  817.         infoPtr->keepFlag = 1;
  818.         continue;
  819.         }
  820.         i++;
  821.  
  822.         if (i == argc) {
  823.         Tcl_AppendResult(interp, "missing value for \"", swtch, "\"",
  824.             (char *)NULL);
  825.         goto error;
  826.         }
  827.         if ((c == 'u') && (strncmp(swtch, "-updatevar", length) == 0)) {
  828.         infoPtr->updateName = strdup(argv[i]);
  829.         } else if ((c == 'e') &&
  830.         (strncmp(swtch, "-errorvar", length) == 0)) {
  831.         infoPtr->errorName = strdup(argv[i]);
  832.         errFilePtr = &errorId;
  833.         } else if ((c == 'o') &&
  834.         (strncmp(swtch, "-outputvar", length) == 0)) {
  835.         infoPtr->outputName = strdup(argv[i]);
  836.         } else if ((c == 's') &&
  837.         (strncmp(swtch, "-statusvar", length) == 0)) {
  838.         infoPtr->statusName = strdup(argv[i]);
  839.         } else if ((c == 'k') && (length > 1) &&
  840.         (strncmp(swtch, "-killsignal", length) == 0)) {
  841.         int value;
  842.  
  843.         if (Tcl_GetInt(interp, argv[i], &value) != TCL_OK) {
  844.             goto error;
  845.         }
  846.         if ((value < 0) || (value > 31)) {
  847.             Tcl_AppendResult(interp, "bad kill signal number \"",
  848.             argv[i], "\"", (char *)NULL);
  849.         }
  850.         infoPtr->sigNum = (unsigned int)value;
  851.         } else {
  852.         Tcl_AppendResult(interp, "bad switch \"", swtch, "\": ",
  853.             "should be -errorvar, -keepnewline, -killsignal, ",
  854.             "-outputvar, -updatevar, or --", (char *)NULL);
  855.         goto error;
  856.         }
  857.     } else {
  858.         if (infoPtr->outputName == NULL) {
  859.         infoPtr->outputName = strdup(argv[i++]);
  860.         }
  861.         break;
  862.     }
  863.     }
  864.     if ((infoPtr->outputName == NULL) || (argc == i)) {
  865.     Tcl_AppendResult(interp, "missing command: should be \"", argv[0],
  866.         " ?switches? varName command ?args?\"", (char *)NULL);
  867.     goto error;
  868.     }
  869.     numPids = Tcl_CreatePipeline(interp, argc - i, argv + i, &pidPtr,
  870.     (int *)NULL, &outputId, errFilePtr);
  871.     if (numPids < 0) {
  872.     goto error;
  873.     }
  874.     infoPtr->outputId = outputId;
  875.     infoPtr->errorId = errorId;
  876.     infoPtr->numPids = (unsigned int)numPids;
  877.     infoPtr->pidPtr = pidPtr;
  878.     infoPtr->lastCount = 0;
  879.     infoPtr->fixMark = -1;
  880.  
  881.     /*
  882.      * Put a trace on the output variable.  The will also allow the
  883.      * subprocesses to be terminated prematurely by the user.
  884.      */
  885.     Tcl_TraceVar(interp, infoPtr->outputName,
  886.     (TCL_TRACE_WRITES | TCL_GLOBAL_ONLY), CleanupProc,
  887.     (ClientData)infoPtr);
  888.  
  889.     if (outputId == -1) {
  890.  
  891.     /*
  892.      * If output has been redirected, start polling immediately
  893.      * for the exit status of each process.  Normally, this is
  894.      * delayed until after standard output has been closed by the
  895.      * last process.  I'm guessing about the timer interval.
  896.      */
  897.  
  898.     infoPtr->timerToken = Tk_CreateTimerHandler(1000, StatusProc,
  899.         (ClientData)infoPtr);
  900.     } else {
  901.  
  902.     /* 
  903.      * Make the output descriptor non-blocking and associate it
  904.      * with a file handler routine 
  905.      */
  906.  
  907. #ifdef O_NONBLOCK
  908.     fcntl(outputId, F_SETFL, O_NONBLOCK);
  909. #else
  910.     fcntl(outputId, F_SETFL, O_NDELAY);
  911. #endif
  912.     Tk_CreateFileHandler(outputId, TK_READABLE, BackgroundProc,
  913.         (ClientData)infoPtr);
  914.     }
  915.  
  916.     return TCL_OK;
  917.   error:
  918.     if (infoPtr != NULL) {
  919.     free((char *)infoPtr);
  920.     }
  921.     return TCL_ERROR;
  922. }
  923.  
  924. /*
  925.  *----------------------------------------------------------------------
  926.  *
  927.  * Blt_BgExecInit --
  928.  *
  929.  *    This procedure is invoked to initialize the "bgexec" Tcl
  930.  *    command.  See the user documentation for details on what it
  931.  *    does.
  932.  *
  933.  * Results:
  934.  *    Nothing.
  935.  *
  936.  * Side effects:
  937.  *    See the user documentation.
  938.  *
  939.  *---------------------------------------------------------------------- */
  940. int
  941. Blt_BgExecInit(interp)
  942.     Tcl_Interp *interp;
  943. {
  944.     Tk_Window tkwin;
  945.  
  946.     if (Blt_FindCmd(interp, "blt_bgexec", (ClientData *)NULL) == TCL_OK) {
  947.     Tcl_AppendResult(interp, "\"blt_bgexec\" command already exists",
  948.         (char *)NULL);
  949.     return TCL_ERROR;
  950.     }
  951.     tkwin = Tk_MainWindow(interp);
  952.     if (tkwin == NULL) {
  953.     Tcl_AppendResult(interp, "\"blt_bgexec\" requires Tk", (char *)NULL);
  954.     return TCL_ERROR;
  955.     }
  956.     Tcl_SetVar2(interp, "blt_versions", "blt_bgexec", BGEXEC_VERSION,
  957.     TCL_GLOBAL_ONLY);
  958.     Tcl_CreateCommand(interp, "blt_bgexec", BgExecCmd, (ClientData)tkwin,
  959.     (Tcl_CmdDeleteProc *)NULL);
  960.     return TCL_OK;
  961. }
  962.